// clang-format off

// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2015 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//


/*  CFCalendar.c
    Copyright (c) 2004 - 2015 Apple Inc. and the Swift project authors
    Responsibility: Christopher Kane
*/


#include <CoreFoundation/CFCalendar.h>
#include "CFLocaleInternal.h"
#include "CFInternal.h"
#include "CFPriv.h"
#include <unicode/ucal.h>

// WINOBJC: extra includes for bridging
#include <Foundation/NSCalendar.h>

#define BUFFER_SIZE 512

struct __CFCalendar {
    CFRuntimeBase _base;
    CFStringRef _identifier;    // canonical identifier, never NULL
    CFLocaleRef _locale;
    CFStringRef _localeID;
    CFTimeZoneRef _tz;
    UCalendar *_cal;
};

static Boolean __CFCalendarEqual(CFTypeRef cf1, CFTypeRef cf2) {
    CFCalendarRef calendar1 = (CFCalendarRef)cf1;
    CFCalendarRef calendar2 = (CFCalendarRef)cf2;
    return CFEqual(calendar1->_identifier, calendar2->_identifier);
}

static CFHashCode __CFCalendarHash(CFTypeRef cf) {
    CFCalendarRef calendar = (CFCalendarRef)cf;
    return CFHash(calendar->_identifier);
}

static CFStringRef __CFCalendarCopyDescription(CFTypeRef cf) {
    CFCalendarRef calendar = (CFCalendarRef)cf;
    return CFStringCreateWithFormat(CFGetAllocator(calendar), NULL, CFSTR("<CFCalendar %p [%p]>{identifier = '%@'}"), cf, CFGetAllocator(calendar), calendar->_identifier);
}

static void __CFCalendarDeallocate(CFTypeRef cf) {
    CFCalendarRef calendar = (CFCalendarRef)cf;
    if (calendar->_identifier) CFRelease(calendar->_identifier);
    if (calendar->_locale) CFRelease(calendar->_locale);
    if (calendar->_localeID) CFRelease(calendar->_localeID);
    if (calendar->_tz) CFRelease(calendar->_tz);
    if (calendar->_cal) ucal_close(calendar->_cal);
}

static CFTypeID __kCFCalendarTypeID = _kCFRuntimeNotATypeID;

static const CFRuntimeClass __CFCalendarClass = {
    0,
    "CFCalendar",
    NULL,   // init
    NULL,   // copy
    __CFCalendarDeallocate,
    __CFCalendarEqual,
    __CFCalendarHash,
    NULL,   // 
    __CFCalendarCopyDescription
};

CFTypeID CFCalendarGetTypeID(void) {
    static dispatch_once_t initOnce = 0;
    dispatch_once(&initOnce, ^{ __kCFCalendarTypeID = _CFRuntimeRegisterClass(&__CFCalendarClass); });
    return __kCFCalendarTypeID;
}

CF_PRIVATE UCalendar *__CFCalendarCreateUCalendar(CFStringRef calendarID, CFStringRef localeID, CFTimeZoneRef tz) {
    if (calendarID) {
    CFDictionaryRef components = CFLocaleCreateComponentsFromLocaleIdentifier(kCFAllocatorSystemDefault, localeID);
    CFMutableDictionaryRef mcomponents = CFDictionaryCreateMutableCopy(kCFAllocatorSystemDefault, 0, components);
    CFDictionarySetValue(mcomponents, kCFLocaleCalendarIdentifierKey, calendarID);
    localeID = CFLocaleCreateLocaleIdentifierFromComponents(kCFAllocatorSystemDefault, mcomponents);
    CFRelease(mcomponents);
    CFRelease(components);
    }

    char buffer[BUFFER_SIZE];
    const char *cstr = CFStringGetCStringPtr(localeID, kCFStringEncodingASCII);
    if (NULL == cstr) {
    if (CFStringGetCString(localeID, buffer, BUFFER_SIZE, kCFStringEncodingASCII)) cstr = buffer;
    }
    if (NULL == cstr) {
    if (calendarID) CFRelease(localeID);
    return NULL;
    }
    
    UChar ubuffer[BUFFER_SIZE];
    CFStringRef tznam = CFTimeZoneGetName(tz);
    CFIndex cnt = CFStringGetLength(tznam);
    if (BUFFER_SIZE < cnt) cnt = BUFFER_SIZE;
    CFStringGetCharacters(tznam, CFRangeMake(0, cnt), (UniChar *)ubuffer);

    UErrorCode status = U_ZERO_ERROR;
    UCalendar *cal = ucal_open(ubuffer, cnt, cstr, UCAL_DEFAULT, &status);
    if (calendarID) CFRelease(localeID);
    return cal;
}

static void __CFCalendarSetupCal(CFCalendarRef calendar) {
    calendar->_cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
}

// WINOBJC: CFCalendarIsWeekend and getNextWeekend are useful. Add win32 as a target as well to properly export it for Foundation to access
#if DEPLOYMENT_RUNTIME_SWIFT || TARGET_OS_WIN32
Boolean _CFCalendarIsWeekend(CFCalendarRef calendar, CFAbsoluteTime at) {
    if (calendar->_cal == NULL) {
        __CFCalendarSetupCal(calendar);
    }
    UDate udate = at * 1000.0;
    UErrorCode status = U_ZERO_ERROR;
    UBool result = ucal_isWeekend(calendar->_cal, udate, &status);
    return result;
}

Boolean _CFCalendarGetNextWeekend(CFCalendarRef calendar, _CFCalendarWeekendRange *range) {
    CFIndex weekdaysIndex[7];
    memset(weekdaysIndex, '\0', sizeof(CFIndex)*7);
    CFIndex firstWeekday = CFCalendarGetFirstWeekday(calendar);
    weekdaysIndex[0] = firstWeekday;
    for (CFIndex i = 1; i < 7; i++) {
        weekdaysIndex[i] = (weekdaysIndex[i-1] % 7) + 1;
    }
    UCalendarWeekdayType weekdayTypes[7];
    CFIndex onset = kCFNotFound;
    CFIndex cease = kCFNotFound;
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (!calendar->_cal) {
        return false;
    }

    // WINOBJC: WinObjC's ICU returns UCAL_WEEKDAY and UCAL_WEEKEND with no onset or cease
    // Add calculations for onset or cease using weekday and weekend
    for (CFIndex i = 0; i < 7; i++) {
        UErrorCode status = U_ZERO_ERROR;
        weekdayTypes[i] = ucal_getDayOfWeekType(calendar->_cal, (UCalendarDaysOfWeek)weekdaysIndex[i], &status);
    }
    for (CFIndex i = 0; i < 7; i++) {
        if ((weekdayTypes[i] == UCAL_WEEKEND_ONSET) ||
            (weekdayTypes[i] == UCAL_WEEKEND && weekdayTypes[(i - 1) % 7] == UCAL_WEEKDAY)) {
            onset = weekdaysIndex[i];
        } else if ((weekdayTypes[i] == UCAL_WEEKEND_CEASE) || 
            (weekdayTypes[i] == UCAL_WEEKEND && weekdayTypes[(i + 1) % 7] == UCAL_WEEKDAY)) {
            cease = weekdaysIndex[i];
        }
    }
    BOOL hasWeekend = NO;
    for (CFIndex i = 0; i < 7; i++) {
        if (weekdayTypes[i] == UCAL_WEEKEND || weekdayTypes[i] == UCAL_WEEKEND_ONSET || weekdayTypes[i] == UCAL_WEEKEND_CEASE) {
            hasWeekend = YES;
            break;
        }
    }
    if (!hasWeekend) {
        return false;
    }
    int32_t onsetTime = 0;
    int32_t ceaseTime = 0;
    if (onset != kCFNotFound) {
        UErrorCode status = U_ZERO_ERROR;
        onsetTime = ucal_getWeekendTransition(calendar->_cal, (UCalendarDaysOfWeek)onset, &status);
    }
    if (cease != kCFNotFound) {
        UErrorCode status = U_ZERO_ERROR;
        ceaseTime = ucal_getWeekendTransition(calendar->_cal, (UCalendarDaysOfWeek)cease, &status);
    }
    CFIndex weekendStart = kCFNotFound;
    CFIndex weekendEnd = kCFNotFound;
    if (onset != kCFNotFound) {
        weekendStart = onset;
    } else {
        if (weekdayTypes[0] == UCAL_WEEKEND && weekdayTypes[6] == UCAL_WEEKEND) {
            for (CFIndex i = 5; i >= 0; i--) {
                if (weekdayTypes[i] != UCAL_WEEKEND) {
                    weekendStart = weekdaysIndex[i + 1];
                    break;
                }
            }
        } else {
            for (CFIndex i = 0; i < 7; i++) {
                if (weekdayTypes[i] == UCAL_WEEKEND) {
                    weekendStart = weekdaysIndex[i];
                    break;
                }
            }
        }
    }
    if (cease != kCFNotFound) {
        weekendEnd = cease;
    } else {
        if (weekdayTypes[0] == UCAL_WEEKEND && weekdayTypes[6] == UCAL_WEEKEND) {
            for (CFIndex i = 1; i < 7; i++) {
                if (weekdayTypes[i] != UCAL_WEEKEND) {
                    weekendEnd = weekdaysIndex[i - 1];
                    break;
                }
            }
        } else {
            for (CFIndex i = 6; i >= 0; i--) {
                if (weekdayTypes[i] == UCAL_WEEKEND) {
                    weekendEnd = weekdaysIndex[i];
                    break;
                }
            }
        }
    }
    
    range->onsetTime = onsetTime / 1000.0;
    range->ceaseTime = ceaseTime / 1000.0;
    range->start = weekendStart;
    range->end = weekendEnd;
    
    return true;
}

#endif

static void __CFCalendarZapCal(CFCalendarRef calendar) {
    ucal_close(calendar->_cal);
    calendar->_cal = NULL;
}

CFCalendarRef CFCalendarCopyCurrent(void) {
    CFLocaleRef locale = CFLocaleCopyCurrent();
    CFCalendarRef calID = (CFCalendarRef)CFLocaleGetValue(locale, kCFLocaleCalendarIdentifierKey);
    if (calID) {
        CFCalendarRef calendar = CFCalendarCreateWithIdentifier(kCFAllocatorSystemDefault, (CFStringRef)calID);
        CFCalendarSetLocale(calendar, locale);
    CFRelease(locale);
        return calendar;
    }
    return NULL;
}

Boolean _CFCalendarInitWithIdentifier(CFCalendarRef calendar, CFStringRef identifier) {
    if (identifier != kCFCalendarIdentifierGregorian && identifier != kCFCalendarIdentifierBuddhist && identifier != kCFCalendarIdentifierJapanese && identifier != kCFCalendarIdentifierIslamic && identifier != kCFCalendarIdentifierIslamicCivil && identifier != kCFCalendarIdentifierHebrew && identifier != kCFCalendarIdentifierChinese) {
        if (CFEqual(kCFCalendarIdentifierGregorian, identifier)) identifier = kCFCalendarIdentifierGregorian;
        else if (CFEqual(kCFCalendarIdentifierBuddhist, identifier)) identifier = kCFCalendarIdentifierBuddhist;
        else if (CFEqual(kCFCalendarIdentifierJapanese, identifier)) identifier = kCFCalendarIdentifierJapanese;
        else if (CFEqual(kCFCalendarIdentifierIslamic, identifier)) identifier = kCFCalendarIdentifierIslamic;
        else if (CFEqual(kCFCalendarIdentifierIslamicCivil, identifier)) identifier = kCFCalendarIdentifierIslamicCivil;
        else if (CFEqual(kCFCalendarIdentifierHebrew, identifier)) identifier = kCFCalendarIdentifierHebrew;
        else if (CFEqual(kCFCalendarIdentifierChinese, identifier)) identifier = kCFCalendarIdentifierChinese;
        else return false;
    }
    
    calendar->_identifier = (CFStringRef)CFRetain(identifier);
    calendar->_locale = NULL;
    calendar->_localeID = CFLocaleGetIdentifier(CFLocaleGetSystem());
    calendar->_tz = CFTimeZoneCopyDefault();
    calendar->_cal = NULL;
    return true;
}

CFCalendarRef CFCalendarCreateWithIdentifier(CFAllocatorRef allocator, CFStringRef identifier) {
    if (allocator == NULL) allocator = __CFGetDefaultAllocator();
    __CFGenericValidateType(allocator, CFAllocatorGetTypeID());
    __CFGenericValidateType(identifier, CFStringGetTypeID());
    // return NULL until Chinese calendar is available
    if (identifier != kCFCalendarIdentifierGregorian && identifier != kCFCalendarIdentifierBuddhist && identifier != kCFCalendarIdentifierJapanese && identifier != kCFCalendarIdentifierIslamic && identifier != kCFCalendarIdentifierIslamicCivil && identifier != kCFCalendarIdentifierHebrew && identifier != kCFCalendarIdentifierChinese) {
    if (CFEqual(kCFCalendarIdentifierGregorian, identifier)) identifier = kCFCalendarIdentifierGregorian;
    else if (CFEqual(kCFCalendarIdentifierBuddhist, identifier)) identifier = kCFCalendarIdentifierBuddhist;
    else if (CFEqual(kCFCalendarIdentifierJapanese, identifier)) identifier = kCFCalendarIdentifierJapanese;
    else if (CFEqual(kCFCalendarIdentifierIslamic, identifier)) identifier = kCFCalendarIdentifierIslamic;
    else if (CFEqual(kCFCalendarIdentifierIslamicCivil, identifier)) identifier = kCFCalendarIdentifierIslamicCivil;
    else if (CFEqual(kCFCalendarIdentifierHebrew, identifier)) identifier = kCFCalendarIdentifierHebrew;
    else if (CFEqual(kCFCalendarIdentifierChinese, identifier)) identifier = kCFCalendarIdentifierChinese;
    else return NULL;
    }
    struct __CFCalendar *calendar = NULL;
    uint32_t size = sizeof(struct __CFCalendar) - sizeof(CFRuntimeBase);
    calendar = (struct __CFCalendar *)_CFRuntimeCreateInstance(allocator, CFCalendarGetTypeID(), size, NULL);
    if (NULL == calendar) {
    return NULL;
    }
    calendar->_identifier = (CFStringRef)CFRetain(identifier);
    calendar->_locale = NULL;
    calendar->_localeID = CFLocaleGetIdentifier(CFLocaleGetSystem());
    calendar->_tz = CFTimeZoneCopyDefault();
    calendar->_cal = NULL;
    return (CFCalendarRef)calendar;
}

CFStringRef CFCalendarGetIdentifier(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFStringRef, (NSCalendar*)calendar, calendarIdentifier);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    return calendar->_identifier;
}

CFLocaleRef CFCalendarCopyLocale(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFLocaleRef, (NSCalendar*)calendar, _copyLocale);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    return (CFLocaleRef)CFLocaleCreate(kCFAllocatorSystemDefault, calendar->_localeID);
}

void CFCalendarSetLocale(CFCalendarRef calendar, CFLocaleRef locale) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, (NSCalendar*)calendar, setLocale:(NSLocale*)locale);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    __CFGenericValidateType(locale, CFLocaleGetTypeID());
    CFStringRef localeID = CFLocaleGetIdentifier(locale);
    if (localeID != calendar->_localeID) {
    CFRelease(calendar->_localeID);
    CFRetain(localeID);
    calendar->_localeID = localeID;
        if (calendar->_cal) __CFCalendarZapCal(calendar);
    }
}

CFTimeZoneRef CFCalendarCopyTimeZone(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFTimeZoneRef, (NSCalendar*)calendar, _copyTimeZone);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    return (CFTimeZoneRef)CFRetain(calendar->_tz);
}

void CFCalendarSetTimeZone(CFCalendarRef calendar, CFTimeZoneRef tz) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, (NSCalendar*)calendar, setTimeZone:(NSTimeZone*)tz);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (tz) __CFGenericValidateType(tz, CFTimeZoneGetTypeID());
    if (tz != calendar->_tz) {
    CFRelease(calendar->_tz);
    calendar->_tz = tz ? (CFTimeZoneRef)CFRetain(tz) : CFTimeZoneCopyDefault();
        if (calendar->_cal) __CFCalendarZapCal(calendar);
    }
}

CFIndex CFCalendarGetFirstWeekday(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, (NSCalendar*)calendar, firstWeekday);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    return ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK);
    }
    return -1;
}

void CFCalendarSetFirstWeekday(CFCalendarRef calendar, CFIndex wkdy) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, (NSCalendar*)calendar, setFirstWeekday:wkdy);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    ucal_setAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK, wkdy);
    }
}

CFIndex CFCalendarGetMinimumDaysInFirstWeek(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, (NSCalendar*)calendar, minimumDaysInFirstWeek);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    return calendar->_cal ? ucal_getAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK) : -1;
}

void CFCalendarSetMinimumDaysInFirstWeek(CFCalendarRef calendar, CFIndex mwd) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, (NSCalendar*)calendar, setMinimumDaysInFirstWeek:mwd);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) ucal_setAttribute(calendar->_cal, UCAL_MINIMAL_DAYS_IN_FIRST_WEEK, mwd);
}

CFDateRef CFCalendarCopyGregorianStartDate(CFCalendarRef calendar) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFDateRef, (NSCalendar*)calendar, _gregorianStartDate);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    UErrorCode status = U_ZERO_ERROR;
    UDate udate = calendar->_cal ? ucal_getGregorianChange(calendar->_cal, &status) : 0;
    if (calendar->_cal && U_SUCCESS(status)) {
    CFAbsoluteTime at = (double)udate / 1000.0 - kCFAbsoluteTimeIntervalSince1970;
    return CFDateCreate(CFGetAllocator(calendar), at);
    }
    return NULL;
}

void CFCalendarSetGregorianStartDate(CFCalendarRef calendar, CFDateRef date) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), void, (NSCalendar*)calendar, _setGregorianStartDate:date);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (date) __CFGenericValidateType(date, CFDateGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (!calendar->_cal) return;
    if (!date) {
    UErrorCode status = U_ZERO_ERROR;
    UCalendar *cal = __CFCalendarCreateUCalendar(calendar->_identifier, calendar->_localeID, calendar->_tz);
    UDate udate = cal ? ucal_getGregorianChange(cal, &status) : 0;
    if (cal && U_SUCCESS(status)) {
        status = U_ZERO_ERROR;
        if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status);
    }
    if (cal) ucal_close(cal);
    } else {
    CFAbsoluteTime at = CFDateGetAbsoluteTime(date);
    UDate udate = (at + kCFAbsoluteTimeIntervalSince1970) * 1000.0;
    UErrorCode status = U_ZERO_ERROR;
    if (calendar->_cal) ucal_setGregorianChange(calendar->_cal, udate, &status);
    }
}


static UCalendarDateFields __CFCalendarGetICUFieldCode(CFCalendarUnit unit) {
    switch (unit) {
    case kCFCalendarUnitEra: return UCAL_ERA;
    case kCFCalendarUnitYear: return UCAL_YEAR;
    case kCFCalendarUnitMonth: return UCAL_MONTH;
    case kCFCalendarUnitDay: return UCAL_DAY_OF_MONTH;
    case kCFCalendarUnitHour: return UCAL_HOUR_OF_DAY;
    case kCFCalendarUnitMinute: return UCAL_MINUTE;
    case kCFCalendarUnitSecond: return UCAL_SECOND;
    case kCFCalendarUnitWeek: return UCAL_WEEK_OF_YEAR;
    case kCFCalendarUnitWeekOfYear: return UCAL_WEEK_OF_YEAR;
    case kCFCalendarUnitWeekOfMonth: return UCAL_WEEK_OF_MONTH;
    case kCFCalendarUnitYearForWeekOfYear: return UCAL_YEAR_WOY;
    case kCFCalendarUnitWeekday: return UCAL_DAY_OF_WEEK;
    case kCFCalendarUnitWeekdayOrdinal: return UCAL_DAY_OF_WEEK_IN_MONTH;
    }
    return (UCalendarDateFields)-1;
}

static UCalendarDateFields __CFCalendarGetICUFieldCodeFromChar(char ch) {
    switch (ch) {
    case 'G': return UCAL_ERA;
    case 'y': return UCAL_YEAR;
    case 'M': return UCAL_MONTH;
    case 'l': return UCAL_IS_LEAP_MONTH;
    case 'd': return UCAL_DAY_OF_MONTH;
    case 'h': return UCAL_HOUR;
    case 'H': return UCAL_HOUR_OF_DAY;
    case 'm': return UCAL_MINUTE;
    case 's': return UCAL_SECOND;
    case 'S': return UCAL_MILLISECOND;
    case 'w': return UCAL_WEEK_OF_YEAR;
    case 'W': return UCAL_WEEK_OF_MONTH;
    case 'Y': return UCAL_YEAR_WOY;
    case 'E': return UCAL_DAY_OF_WEEK;
    case 'D': return UCAL_DAY_OF_YEAR;
    case 'F': return UCAL_DAY_OF_WEEK_IN_MONTH;
    case 'a': return UCAL_AM_PM;
    case 'g': return UCAL_JULIAN_DAY;
    }
    return (UCalendarDateFields)-1;
}

static CFCalendarUnit __CFCalendarGetCalendarUnitFromChar(char ch) {
    switch (ch) {
    case 'G': return kCFCalendarUnitEra;
    case 'y': return kCFCalendarUnitYear;
    case 'M': return kCFCalendarUnitMonth;
    case 'd': return kCFCalendarUnitDay;
    case 'H': return kCFCalendarUnitHour;
    case 'm': return kCFCalendarUnitMinute;
    case 's': return kCFCalendarUnitSecond;
    case 'w': return kCFCalendarUnitWeekOfYear;
    case 'W': return kCFCalendarUnitWeekOfMonth;
    case 'Y': return kCFCalendarUnitYearForWeekOfYear;
    case 'E': return kCFCalendarUnitWeekday;
    case 'F': return kCFCalendarUnitWeekdayOrdinal;
    }
    return (UCalendarDateFields)-1;
}

CFRange CFCalendarGetMinimumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) {
    // CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, (NSCalendar*)calendar, _minimumRangeOfUnit:unit);
    // WINOBJC: this function isn't in the header so it assumes id return. change to the one that is available.
    // Also don't use the dispatch macro since a NSRange != a CFRange exactly. Need to manually convert.
    if (CF_IS_OBJC(CFCalendarGetTypeID(), calendar)) { 
        NSRange nsRetVal = CF_OBJC_CALLV((NSCalendar*)calendar, minimumRangeOfUnit:unit);
        return CFRange {nsRetVal.location , nsRetVal.length};
    } 

    CFRange range = {kCFNotFound, kCFNotFound};
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    ucal_clear(calendar->_cal);
    UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
    UErrorCode status = U_ZERO_ERROR;
    range.location = ucal_getLimit(calendar->_cal, field, UCAL_GREATEST_MINIMUM, &status);
    range.length = ucal_getLimit(calendar->_cal, field, UCAL_LEAST_MAXIMUM, &status) - range.location + 1;
    if (UCAL_MONTH == field) range.location++;
    if (100000 < range.length) range.length = 100000;
    }
    return range;
}

CFRange CFCalendarGetMaximumRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit) {
    // WINOBJC: this function isn't in the header so it assumes id return. change to the one that is available.
    // CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, (NSCalendar*)calendar, _maximumRangeOfUnit:unit);
    // Also don't use the dispatch macro since a NSRange != a CFRange exactly. Need to manually convert.
    if (CF_IS_OBJC(CFCalendarGetTypeID(), calendar)) { 
        NSRange nsRetVal = CF_OBJC_CALLV((NSCalendar*)calendar, maximumRangeOfUnit:unit);
        return CFRange {nsRetVal.location , nsRetVal.length};
    } 

    CFRange range = {kCFNotFound, kCFNotFound};
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    ucal_clear(calendar->_cal);
    UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
    UErrorCode status = U_ZERO_ERROR;
    range.location = ucal_getLimit(calendar->_cal, field, UCAL_MINIMUM, &status);
    range.length = ucal_getLimit(calendar->_cal, field, UCAL_MAXIMUM, &status) - range.location + 1;
    if (UCAL_MONTH == field) range.location++;
    if (100000 < range.length) range.length = 100000;
    }
    return range;
}

static void __CFCalendarSetToFirstInstant(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at) {
    // Set UCalendar to first instant of unit prior to 'at'
    UErrorCode status = U_ZERO_ERROR;
    UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    ucal_setMillis(calendar->_cal, udate, &status);
    int target_era = INT_MIN;
    switch (unit) { // largest to smallest, we set the fields to their minimum value
    case kCFCalendarUnitYearForWeekOfYear:;
        ucal_set(calendar->_cal, UCAL_WEEK_OF_YEAR, ucal_getLimit(calendar->_cal, UCAL_WEEK_OF_YEAR, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitWeek:
    case kCFCalendarUnitWeekOfMonth:;
    case kCFCalendarUnitWeekOfYear:;
    {
    // reduce to first day of week, then reduce the rest of the day
        int32_t goal = ucal_getAttribute(calendar->_cal, UCAL_FIRST_DAY_OF_WEEK);
    int32_t dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
    while (dow != goal) {
        ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, -1, &status);
        dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
    }
    goto day;
    }
    case kCFCalendarUnitEra:
    {
    target_era = ucal_get(calendar->_cal, UCAL_ERA, &status);
    ucal_set(calendar->_cal, UCAL_YEAR, ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_ACTUAL_MINIMUM, &status));
    }
    case kCFCalendarUnitYear:
    ucal_set(calendar->_cal, UCAL_MONTH, ucal_getLimit(calendar->_cal, UCAL_MONTH, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitMonth:
    ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, ucal_getLimit(calendar->_cal, UCAL_DAY_OF_MONTH, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitWeekday:
    case kCFCalendarUnitDay:
    day:;
    ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, ucal_getLimit(calendar->_cal, UCAL_HOUR_OF_DAY, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitHour:
    ucal_set(calendar->_cal, UCAL_MINUTE, ucal_getLimit(calendar->_cal, UCAL_MINUTE, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitMinute:
    ucal_set(calendar->_cal, UCAL_SECOND, ucal_getLimit(calendar->_cal, UCAL_SECOND, UCAL_ACTUAL_MINIMUM, &status));
    case kCFCalendarUnitSecond:
    ucal_set(calendar->_cal, UCAL_MILLISECOND, 0);
    }
    if (INT_MIN != target_era && ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
    // In the Japanese calendar, and possibly others, eras don't necessarily
    // start on the first day of a year, so the previous code may have backed
    // up into the previous era, and we have to correct forward.
    UDate bad_udate = ucal_getMillis(calendar->_cal, &status);
    ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
    while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
        bad_udate = ucal_getMillis(calendar->_cal, &status);
        ucal_add(calendar->_cal, UCAL_MONTH, 1, &status);
    }
    udate = ucal_getMillis(calendar->_cal, &status);
    // target date is between bad_udate and udate
    for (;;) {
        UDate test_udate = (udate + bad_udate) / 2;
        ucal_setMillis(calendar->_cal, test_udate, &status);
        if (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era) {
        bad_udate = test_udate;
        } else {
        udate = test_udate;
        }
        if (fabs(udate - bad_udate) < 1000) break;
    }
    do {
        bad_udate = floor((bad_udate + 1000) / 1000) * 1000;
        ucal_setMillis(calendar->_cal, bad_udate, &status);
    } while (ucal_get(calendar->_cal, UCAL_ERA, &status) < target_era);
    }
}

static Boolean __validUnits(CFCalendarUnit smaller, CFCalendarUnit bigger) {
    switch (bigger) {
    case kCFCalendarUnitEra:
    if (kCFCalendarUnitEra == smaller) return false;
    if (kCFCalendarUnitWeekday == smaller) return false;
    if (kCFCalendarUnitMinute == smaller) return false; // this causes CFIndex overflow in range.length
    if (kCFCalendarUnitSecond == smaller) return false; // this causes CFIndex overflow in range.length
    return true;
    case kCFCalendarUnitYearForWeekOfYear:
    case kCFCalendarUnitYear:
    if (kCFCalendarUnitEra == smaller) return false;
    if (kCFCalendarUnitYear == smaller) return false;
        if (kCFCalendarUnitYearForWeekOfYear == smaller) return false;
    if (kCFCalendarUnitWeekday == smaller) return false;
    return true;
    case kCFCalendarUnitMonth:
    if (kCFCalendarUnitEra == smaller) return false;
    if (kCFCalendarUnitYear == smaller) return false;
    if (kCFCalendarUnitMonth == smaller) return false;
    if (kCFCalendarUnitWeekday == smaller) return false;
    return true;
    case kCFCalendarUnitDay:
    if (kCFCalendarUnitHour == smaller) return true;
    if (kCFCalendarUnitMinute == smaller) return true;
    if (kCFCalendarUnitSecond == smaller) return true;
    return false;
    case kCFCalendarUnitHour:
    if (kCFCalendarUnitMinute == smaller) return true;
    if (kCFCalendarUnitSecond == smaller) return true;
    return false;
    case kCFCalendarUnitMinute:
    if (kCFCalendarUnitSecond == smaller) return true;
    return false;
    case kCFCalendarUnitWeek:
    case kCFCalendarUnitWeekOfMonth:
    case kCFCalendarUnitWeekOfYear:
    if (kCFCalendarUnitWeekday == smaller) return true;
    if (kCFCalendarUnitDay == smaller) return true;
    if (kCFCalendarUnitHour == smaller) return true;
    if (kCFCalendarUnitMinute == smaller) return true;
    if (kCFCalendarUnitSecond == smaller) return true;
    return false;
    case kCFCalendarUnitSecond:
    case kCFCalendarUnitWeekday:
    case kCFCalendarUnitWeekdayOrdinal:
    return false;
    }
    return false;
};

static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) __attribute__((noinline));
static CFRange __CFCalendarGetRangeOfUnit2(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
    // WINOBJC: this function isn't in the header so it assumes id return. change to the one that is available.
    // CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFRange, (NSCalendar*)calendar, _rangeOfUnit:smallerUnit inUnit:biggerUnit forAT:at);
    // Also don't use the dispatch macro since a NSRange != a CFRange exactly. Need to manually convert.
    if (CF_IS_OBJC(CFCalendarGetTypeID(), calendar)) { 
        NSRange nsRetVal = CF_OBJC_CALLV((NSCalendar*)calendar, rangeOfUnit:smallerUnit inUnit:biggerUnit forDate:(NSDate*)CFDateCreate(nullptr, at));
        return CFRange {nsRetVal.location , nsRetVal.length};
    } 

    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    CFRange range = {kCFNotFound, kCFNotFound};
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    switch (smallerUnit) {
    case kCFCalendarUnitSecond:
            switch (biggerUnit) {
            case kCFCalendarUnitMinute:
            case kCFCalendarUnitHour:
            case kCFCalendarUnitDay:
            case kCFCalendarUnitWeekday:
            // WINOBJC: WeekOfYear and WeekOfMonth are also valid "biggerUnit"s.
            case kCFCalendarUnitWeekOfYear:
            case kCFCalendarUnitWeekOfMonth:
            case kCFCalendarUnitWeek:
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        // goto calculate;
                range.location = 0;
                range.length = 60;
        break;
            }
        break;
    case kCFCalendarUnitMinute:
            switch (biggerUnit) {
            case kCFCalendarUnitHour:
            case kCFCalendarUnitDay:
            case kCFCalendarUnitWeekday:
            // WINOBJC: WeekOfYear and WeekOfMonth are also valid "biggerUnit"s.
            case kCFCalendarUnitWeekOfYear:
            case kCFCalendarUnitWeekOfMonth:
            case kCFCalendarUnitWeek:
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        // goto calculate;
                range.location = 0;
                range.length = 60;
        break;
            }
        break;
    case kCFCalendarUnitHour:
            switch (biggerUnit) {
            case kCFCalendarUnitDay:
            case kCFCalendarUnitWeekday:
            // WINOBJC: WeekOfYear and WeekOfMonth are also valid "biggerUnit"s.
            case kCFCalendarUnitWeekOfYear:
            case kCFCalendarUnitWeekOfMonth:
            case kCFCalendarUnitWeek:
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        // goto calculate;
                range.location = 0;
                range.length = 24;
        break;
            }
        break;
    case kCFCalendarUnitDay:
            switch (biggerUnit) {
            // WINOBJC: WeekOfYear and WeekOfMonth are also valid "biggerUnit"s.
            case kCFCalendarUnitWeekOfYear:
            case kCFCalendarUnitWeekOfMonth:
            case kCFCalendarUnitWeek:
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    case kCFCalendarUnitWeekday:
            switch (biggerUnit) {
            // WINOBJC: WeekOfYear and WeekOfMonth are also valid "biggerUnit"s.
            case kCFCalendarUnitWeekOfYear:
            case kCFCalendarUnitWeekOfMonth:
            case kCFCalendarUnitWeek:
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    case kCFCalendarUnitWeekdayOrdinal:
            switch (biggerUnit) {
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    // WINOBJC: Add cases for weekOf units. ICU will handle them below.
    case kCFCalendarUnitWeekOfYear:
    case kCFCalendarUnitWeekOfMonth:
    case kCFCalendarUnitWeek:
            switch (biggerUnit) {
            case kCFCalendarUnitMonth:
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    case kCFCalendarUnitMonth:
            switch (biggerUnit) {
            case kCFCalendarUnitYear:
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    case kCFCalendarUnitYear:
            switch (biggerUnit) {
            case kCFCalendarUnitEra:
        goto calculate;
        break;
            }
        break;
    case kCFCalendarUnitEra:
        break;
    }
    }
    return range;

    // TODO 8463791 : rangeOfUnit can return incorrect results
    calculate:;
    ucal_clear(calendar->_cal);
    UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit);
    UCalendarDateFields bigField = __CFCalendarGetICUFieldCode(biggerUnit);
    UCalendarDateFields yearField = __CFCalendarGetICUFieldCode(kCFCalendarUnitYear);
    UCalendarDateFields fieldToAdd = smallField;
    if (kCFCalendarUnitWeekday == smallerUnit) {
        fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitDay);
    }
    int32_t dow = -1;
    if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
        UErrorCode status = U_ZERO_ERROR;
        UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
        ucal_setMillis(calendar->_cal, udate, &status);
        dow = ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status);
        fieldToAdd = __CFCalendarGetICUFieldCode(kCFCalendarUnitWeek);
    }
    // Set calendar to first instant of big unit
    __CFCalendarSetToFirstInstant(calendar, biggerUnit, at);
    if (kCFCalendarUnitWeekdayOrdinal == smallerUnit) {
        UErrorCode status = U_ZERO_ERROR;
        // roll day forward to first 'dow'
        while (ucal_get(calendar->_cal, (kCFCalendarUnitMonth == biggerUnit) ? UCAL_WEEK_OF_MONTH : UCAL_WEEK_OF_YEAR, &status) != 1) {
        ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
        }
        while (ucal_get(calendar->_cal, UCAL_DAY_OF_WEEK, &status) != dow) {
        ucal_add(calendar->_cal, UCAL_DAY_OF_MONTH, 1, &status);
        }
    }
    int32_t minSmallValue = INT32_MAX;
    int32_t maxSmallValue = INT32_MIN;
    UErrorCode status = U_ZERO_ERROR;
    int32_t bigValue = ucal_get(calendar->_cal, bigField, &status);
    for (;;) {
        int32_t smallValue = ucal_get(calendar->_cal, smallField, &status);
        if (smallValue < minSmallValue) minSmallValue = smallValue;
        if (smallValue > maxSmallValue) maxSmallValue = smallValue;
        ucal_add(calendar->_cal, fieldToAdd, 1, &status);
        if (bigValue != ucal_get(calendar->_cal, bigField, &status)) break;
        if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) break;
    // we assume an answer for 10000 years can be extrapolated to 100000 years, to save time
    }
    status = U_ZERO_ERROR;
    range.location = minSmallValue;
    if (smallerUnit == kCFCalendarUnitMonth) range.location = 1;
    range.length = maxSmallValue - minSmallValue + 1;
    if (biggerUnit == kCFCalendarUnitEra && ucal_get(calendar->_cal, yearField, &status) > 10000) range.length = 100000;

    return range;
}

CFRange CFCalendarGetRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
    return __CFCalendarGetRangeOfUnit2(calendar, smallerUnit, biggerUnit, at);
}

CFIndex CFCalendarGetOrdinalityOfUnit(CFCalendarRef calendar, CFCalendarUnit smallerUnit, CFCalendarUnit biggerUnit, CFAbsoluteTime at) {
    CFIndex result = kCFNotFound;
    if (!__validUnits(smallerUnit, biggerUnit)) return result;
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), CFIndex, (NSCalendar*)calendar, _ordinalityOfUnit:smallerUnit inUnit:biggerUnit forAT:at);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    UErrorCode status = U_ZERO_ERROR;
    ucal_clear(calendar->_cal);
    if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitYear == biggerUnit) {
        UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
        ucal_setMillis(calendar->_cal, udate, &status);
        int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_YEAR, &status);
        return val;
    } else if (kCFCalendarUnitWeek == smallerUnit && kCFCalendarUnitMonth == biggerUnit) {
        UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
        ucal_setMillis(calendar->_cal, udate, &status);
        int32_t val = ucal_get(calendar->_cal, UCAL_WEEK_OF_MONTH, &status);
        return val;
    }
    UCalendarDateFields smallField = __CFCalendarGetICUFieldCode(smallerUnit);
    // Set calendar to first instant of big unit
    __CFCalendarSetToFirstInstant(calendar, biggerUnit, at);
    UDate curr = ucal_getMillis(calendar->_cal, &status);
        UDate goal = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    result = 1;
    const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14};
    int multiple = (1 << multiple_table[flsl(smallerUnit) - 1]);
    Boolean divide = false, alwaysDivide = false;
    while (curr < goal) {
        ucal_add(calendar->_cal, smallField, multiple, &status);
        UDate newcurr = ucal_getMillis(calendar->_cal, &status);
        if (curr < newcurr && newcurr <= goal) {
        result += multiple;
        curr = newcurr;
        } else {
        // Either newcurr is going backwards, or not making
        // progress, or has overshot the goal; reset date
        // and try smaller multiples.
        ucal_setMillis(calendar->_cal, curr, &status);
        divide = true;
        // once we start overshooting the goal, the add at
        // smaller multiples will succeed at most once for
        // each multiple, so we reduce it every time through
        // the loop.
        if (goal < newcurr) alwaysDivide = true;
        }
        if (divide) {
        multiple = multiple / 2;
        if (0 == multiple) break;
        divide = alwaysDivide;
        }
    }
    }
    return result;
}

Boolean _CFCalendarComposeAbsoluteTimeV(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, int *vector, int count) {
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    UErrorCode status = U_ZERO_ERROR;
    ucal_clear(calendar->_cal);
    ucal_set(calendar->_cal, UCAL_YEAR, 1);
    ucal_set(calendar->_cal, UCAL_MONTH, 0);
    ucal_set(calendar->_cal, UCAL_DAY_OF_MONTH, 1);
    ucal_set(calendar->_cal, UCAL_HOUR_OF_DAY, 0);
    ucal_set(calendar->_cal, UCAL_MINUTE, 0);
    ucal_set(calendar->_cal, UCAL_SECOND, 0);
    const char *desc = componentDesc;
    Boolean doWOY = false;
    char ch = *desc;
    while (ch) {
        UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
        if (UCAL_WEEK_OF_YEAR == field) {
        doWOY = true;
        }
        desc++;
        ch = *desc;
    }
    desc = componentDesc;
    ch = *desc;
    while (ch) {
        UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
        int value = *vector;
        if (UCAL_YEAR == field && doWOY) field = UCAL_YEAR_WOY;
        if (UCAL_MONTH == field) value--;
        ucal_set(calendar->_cal, field, value);
        vector++;
        desc++;
        ch = *desc;
    }
    UDate udate = ucal_getMillis(calendar->_cal, &status);
    CFAbsoluteTime at = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970;
        if (atp) *atp = at;
    return U_SUCCESS(status) ? true : false;
    }
    return false;
}

Boolean _CFCalendarDecomposeAbsoluteTimeV(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, int **vector, int count) {
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    UErrorCode status = U_ZERO_ERROR;
    ucal_clear(calendar->_cal);
    UDate udate = floor((at + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    ucal_setMillis(calendar->_cal, udate, &status);
    char ch = *componentDesc;
    while (ch) {
        UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
        int value = ucal_get(calendar->_cal, field, &status);
        if (UCAL_MONTH == field) value++;
        *(*vector) = value;
        vector++;
        componentDesc++;
        ch = *componentDesc;
    }
    return U_SUCCESS(status) ? true : false;
    }
    return false;
}

Boolean _CFCalendarAddComponentsV(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, int *vector, int count) {
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    UErrorCode status = U_ZERO_ERROR;
    ucal_clear(calendar->_cal);
    UDate udate = floor((*atp + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    ucal_setMillis(calendar->_cal, udate, &status);
    char ch = *componentDesc;
    while (ch) {
        // WINOBJC: ICU function ucal_add does not accept Nanoseconds. Skip this component as it causes failure.
        if (ch == '#') {
            vector++;
            componentDesc++;
            ch = *componentDesc;
            continue;
        }
        UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
            int amount = *vector;
        if (options & kCFCalendarComponentsWrap) {
        ucal_roll(calendar->_cal, field, amount, &status);
        } else {
        ucal_add(calendar->_cal, field, amount, &status);
        }
        vector++;
        componentDesc++;
        ch = *componentDesc;
    }
    udate = ucal_getMillis(calendar->_cal, &status);
    *atp = (udate / 1000.0) - kCFAbsoluteTimeIntervalSince1970;
    return U_SUCCESS(status) ? true : false;
    }
    return false;
}

Boolean _CFCalendarGetComponentDifferenceV(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, int **vector, int count) {
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
    UErrorCode status = U_ZERO_ERROR;
    ucal_clear(calendar->_cal);
    UDate curr = floor((startingAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    UDate goal = floor((resultAT + kCFAbsoluteTimeIntervalSince1970) * 1000.0);
    ucal_setMillis(calendar->_cal, curr, &status);
    int direction = (startingAT <= resultAT) ? 1 : -1;
    char ch = *componentDesc;
    while (ch) {
        // WINOBJC: ICU function ucal_add does not accept UCAL_LEAP_MONTH. Skip this component as it causes failure. TODO: 7682424 ICU update may fix this
        if (ch == 'l') {
            vector++;
            componentDesc++;
            ch = *componentDesc;
            continue;
        }
        UCalendarDateFields field = __CFCalendarGetICUFieldCodeFromChar(ch);
        const int multiple_table[] = {0, 0, 16, 19, 24, 26, 24, 28, 14, 14, 14};
        int multiple = direction * (1 << multiple_table[flsl(__CFCalendarGetCalendarUnitFromChar(ch)) - 1]);
        Boolean divide = false, alwaysDivide = false;
        int result = 0;
        while ((direction > 0 && curr < goal) || (direction < 0 && goal < curr)) {
            ucal_add(calendar->_cal, field, multiple, &status);
            UDate newcurr = ucal_getMillis(calendar->_cal, &status);
            if ((direction > 0 && curr < newcurr && newcurr <= goal) || (direction < 0 && newcurr < curr && goal <= newcurr)) {
                result += multiple;
                curr = newcurr;
            } else {
                // Either newcurr is going backwards, or not making
                // progress, or has overshot the goal; reset date
                // and try smaller multiples.
                ucal_setMillis(calendar->_cal, curr, &status);
                divide = true;
                // once we start overshooting the goal, the add at
                // smaller multiples will succeed at most once for
                // each multiple, so we reduce it every time through
                // the loop.
                if ((direction > 0 && goal < newcurr) || (direction < 0 && newcurr < goal)) alwaysDivide = true;
            }
            if (divide) {
                multiple = multiple / 2;
                if (0 == multiple) break;
                divide = alwaysDivide;
            }
        }
        *(*vector) = result;   
        vector++;
        componentDesc++;
        ch = *componentDesc;
    }
    return U_SUCCESS(status) ? true : false;
    }
    return false;
}

Boolean CFCalendarComposeAbsoluteTime(CFCalendarRef calendar, /* out */ CFAbsoluteTime *atp, const char *componentDesc, ...) {
    va_list args;
    va_start(args, componentDesc);
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar*)calendar, _composeAbsoluteTime:atp :componentDesc :args);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    int idx, cnt = strlen((char *)componentDesc);
    STACK_BUFFER_DECL(int, vector, cnt);
    for (idx = 0; idx < cnt; idx++) {
    int arg = va_arg(args, int);
    vector[idx] = arg;
    }
    va_end(args);
    return _CFCalendarComposeAbsoluteTimeV(calendar, atp, componentDesc, vector, cnt);
}

Boolean CFCalendarDecomposeAbsoluteTime(CFCalendarRef calendar, CFAbsoluteTime at, const char *componentDesc, ...) {
    va_list args;
    va_start(args, componentDesc);
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar*)calendar, _decomposeAbsoluteTime:at :componentDesc :args);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    int idx, cnt = strlen((char *)componentDesc);
    STACK_BUFFER_DECL(int *, vector, cnt);
    for (idx = 0; idx < cnt; idx++) {
    int *arg = va_arg(args, int *);
    vector[idx] = arg;
    }
    va_end(args);
    return _CFCalendarDecomposeAbsoluteTimeV(calendar, at, componentDesc, vector, cnt);
}

Boolean CFCalendarAddComponents(CFCalendarRef calendar, /* inout */ CFAbsoluteTime *atp, CFOptionFlags options, const char *componentDesc, ...) {
    va_list args;
    va_start(args, componentDesc);
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar*)calendar, _addComponents:atp :options :componentDesc :args);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    int idx, cnt = strlen((char *)componentDesc);
    STACK_BUFFER_DECL(int, vector, cnt);
    for (idx = 0; idx < cnt; idx++) {
    int arg = va_arg(args, int);
    vector[idx] = arg;
    }
    va_end(args);
    return _CFCalendarAddComponentsV(calendar, atp, options, componentDesc, vector, cnt);    
}

Boolean CFCalendarGetComponentDifference(CFCalendarRef calendar, CFAbsoluteTime startingAT, CFAbsoluteTime resultAT, CFOptionFlags options, const char *componentDesc, ...) {
    va_list args;
    va_start(args, componentDesc);
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar*)calendar, _diffComponents:startingAT :resultAT :options :componentDesc :args);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    int idx, cnt = strlen((char *)componentDesc);
    STACK_BUFFER_DECL(int *, vector, cnt);
    for (idx = 0; idx < cnt; idx++) {
    int *arg = va_arg(args, int *);
    vector[idx] = arg;
    }
    va_end(args);
    Boolean ret = _CFCalendarGetComponentDifferenceV(calendar, startingAT, resultAT, options, componentDesc, vector, cnt);
    return ret;
}

Boolean CFCalendarGetTimeRangeOfUnit(CFCalendarRef calendar, CFCalendarUnit unit, CFAbsoluteTime at, CFAbsoluteTime *startp, CFTimeInterval *tip) {
    CF_OBJC_FUNCDISPATCHV(CFCalendarGetTypeID(), Boolean, (NSCalendar*)calendar, _rangeOfUnit:unit startTime:startp interval:tip forAT:at);
    __CFGenericValidateType(calendar, CFCalendarGetTypeID());
    if (kCFCalendarUnitWeekdayOrdinal == unit) return false;
    if (kCFCalendarUnitWeekday == unit) unit = kCFCalendarUnitDay;
    if (!calendar->_cal) __CFCalendarSetupCal(calendar);
    if (calendar->_cal) {
        ucal_clear(calendar->_cal);
        __CFCalendarSetToFirstInstant(calendar, unit, at);
        UErrorCode status = U_ZERO_ERROR;
        UDate start = ucal_getMillis(calendar->_cal, &status);
    UCalendarDateFields field = __CFCalendarGetICUFieldCode(unit);
    ucal_add(calendar->_cal, field, 1, &status);
        UDate end = ucal_getMillis(calendar->_cal, &status);
    if (end == start && kCFCalendarUnitEra == unit) {
            // ICU refuses to do the addition, probably because we are
            // at the limit of UCAL_ERA.  Use alternate strategy.
            CFIndex limit = ucal_getLimit(calendar->_cal, UCAL_YEAR, UCAL_MAXIMUM, &status);
            if (100000 < limit) limit = 100000;
            ucal_add(calendar->_cal, UCAL_YEAR, limit, &status);
        end = ucal_getMillis(calendar->_cal, &status);
    }
    if (U_SUCCESS(status)) {
        if (startp) *startp = (double)start / 1000.0 - kCFAbsoluteTimeIntervalSince1970;
        if (tip) *tip = (double)(end - start) / 1000.0;
        return true;
    }
    }

    return false;
}

#undef BUFFER_SIZE

// clang-format on
